﻿using System;
using System.Text;
using System.Collections;
using Microsoft.SPOT;

namespace GPS.GPSLanguage
{
    class NMEAParser
    {
        private GPSData gpsDataGSV;

        public delegate void GPSDataReadyEvent(NMEAParser sender, GPSData data);

        /// <summary>
        /// Fires when any GPS message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPSDataReady;

        /// <summary>
        /// Fires when a GPGGA message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPGGADataReady;

        /// <summary>
        /// Fires when a GPGSA message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPGSADataReady;

        /// <summary>
        /// Fires when a GPRMC message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPRMCDataReady;

        /// <summary>
        /// Fires when a GPGSV message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPGSVDataReady;

        /// <summary>
        /// Fires when a GPGSV message has been fully parsed
        /// </summary>
        public event GPSDataReadyEvent GPGLLDataReady;

        public NMEAParser()
        {
            gpsDataGSV = new GPSData();
        }

        public void ParseGPSString(string sentence, bool IgnoreErrors)
        {
            try
            {
                ParseIt(sentence);
            }
            catch(Exception ex)
            {
                if (!IgnoreErrors)
                {
                    throw ex;
                }
            }
            
        }

        private void ParseIt(string sentence)
        {
            if (!HasValidChecksum(sentence))
            {
                throw new InvalidChecksumException("The sentence didn't have a valid checksum");
            }

            GPSData gpsData = new GPSData();
            Queue q = new Queue();
            try
            {
                sentence = sentence.Substring(1, sentence.Length - 4);
                String[] tokens = sentence.Split(',');

                foreach (String s in tokens)
                {
                    q.Enqueue(s);
                }
            }
            catch
            {
                throw new Exception("Unable to break the string up");
            }

            try
            {
                gpsData = MessageTypeToken.ParseLanguage(gpsData, q, gpsDataGSV);
            }
            catch (GSVOutOfOrderException)//I'll catch and ignore this error, but you deal with the rest
            {
                gpsDataGSV = new GPSData();
            }

            if (gpsData.Complete)
            {
                if (GPSDataReady != null)
                    GPSDataReady(this, gpsData);

                switch (gpsData.MessageType)
                {
                    case MessageType.GPGGA:
                        if(GPGGADataReady!=null)
                            GPGGADataReady(this, gpsData);
                        break;
                    case MessageType.GPGSA:
                        if(GPGSADataReady!=null)
                            GPGSADataReady(this, gpsData);
                        break;
                    case MessageType.GPGSV:
                        if(GPGSVDataReady!=null)
                            GPGSVDataReady(this, gpsData);
                            gpsDataGSV = new GPSData();
                        break;
                    case MessageType.GPRMC:
                        if(GPRMCDataReady!=null)
                            GPRMCDataReady(this, gpsData);
                        break;
                    case MessageType.GPGLL:
                        if (GPGLLDataReady != null)
                            GPGLLDataReady(this, gpsData);
                        break;
                }
            }
            
        }

        #region Simple Checksum validation
        public static bool HasValidChecksum(string sentence)
        {
            //For assumption purposes "$[at least one character]*XX"
            if (sentence.Length < 5)
                return false;

            //$ = String, but Dollar looks more descriptive
            int DollarLoc = sentence.IndexOf('$');
            int StarLoc = sentence.LastIndexOf('*');

            //make sure this string doesn't have extraneous characters
            if (DollarLoc != 0 || StarLoc != sentence.Length - 3 )
                return false;

            string checksumFromSentence = sentence.Substring(StarLoc + 1, 2);


            String valToCheck = sentence.Substring(DollarLoc+1, StarLoc-1);
            
            //the checksum is calculated by xor-ing all the characters between $ and *
            char c = valToCheck[0];
            for(int i = 1;i<valToCheck.Length;i++)
            {
                c ^= valToCheck[i];
            }

            return Convert.ToInt32(checksumFromSentence,16) == (int)c;
        }
        #endregion
    }
}
